package com.yzd.learnjava.concurrency;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import com.yzd.learnjava.util.Print;


public class CriticalSection {
	static void testApproaches(PairManager pm1, PairManager pm2) {
		ExecutorService exec = Executors.newCachedThreadPool();
		PairManipulator pmu1 = new PairManipulator(pm1);
		PairManipulator pmu2 = new PairManipulator(pm2);
		
		PairChecker pc1 = new PairChecker(pm1);
		PairChecker pc2 = new PairChecker(pm2);
		
		exec.execute(pmu1);
		exec.execute(pmu2);
		
		exec.execute(pc1);
		exec.execute(pc2);
		
		try {
			TimeUnit.MILLISECONDS.sleep(500);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		Print.print("pmu1:" + pmu1 + "\npmu2: " + pmu2);
		System.exit(0);
	}
	
	public static void main(String[] args) {
		PairManager pm1 = new PairManager1();
		PairManager pm2 = new PairManager2();
		
		testApproaches(pm1, pm2);
	}
}

class Pair{
	private int x ;
	private int y ;
	public Pair(int x , int y) {
		this.x = x ;
		this.y = y ;
	}
	
	public Pair() {
		this(0, 0);
	}
	public int getX() {return x;}
	public int getY() {return y;}
	public void inCrementX() {x++;}
	public void inCrementY() {y++;}
	public String toString() {
		return "x: " + x + ", y: " + y;
	}
	
	public class PairValuesNotEqualExeception extends RuntimeException{
		public PairValuesNotEqualExeception() {
			super("Pair valus not equal : " + Pair.this);
		}
	}
	
	public void checkState() {
		if(x != y) {
			throw new PairValuesNotEqualExeception();
		}
	}
}

abstract class PairManager{
	AtomicInteger checkCounter = new AtomicInteger(0);
	protected Pair p = new Pair();
	private List<Pair> storage = Collections.synchronizedList(new ArrayList<Pair>());
	public synchronized Pair getPair() {
		return new Pair(p.getX(), p.getY());
	}
	protected void store(Pair p) {
		storage.add(p);
		try {
			TimeUnit.MILLISECONDS.sleep(50);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public abstract void inCrement();
}

class PairManager1 extends PairManager{
	public synchronized void inCrement() {
		p.inCrementX();
		p.inCrementY();
		store(p);
	}
}

class PairManager2 extends PairManager{
	public void inCrement() {
		Pair temp ;
		synchronized (this) {
			p.inCrementX();
			p.inCrementY();
			temp = new Pair();
		}
		store(temp);
	}
}

class PairManipulator implements Runnable{
	private PairManager pm ;
	public PairManipulator(PairManager pm) {
		this.pm = pm;
	}
	public void run() {
		while(true) {
			pm.inCrement();
		}
	}
	
	public String toString() {
		return "Pair : " + pm.getPair() + " checkCounter = " + pm.checkCounter.get();
	}
}

class PairChecker implements Runnable{
	private PairManager pm ;
	public PairChecker(PairManager pm) {
		this.pm = pm;
	}
	
	public void run() {
		while(true) {
			pm.checkCounter.incrementAndGet();
			pm.getPair().checkState();
		}
	}
}
























